home *** CD-ROM | disk | FTP | other *** search
Text File | 1995-11-01 | 31.2 KB | 1,231 lines |
- // Copyright (C) 1995 Steve Hayman
- // Use is governed by the MiscKit license
-
- #import <misckit/MiscShell.h>
-
- /*
- * $Header: /SAHayman/LocalDeveloper/Source/MiscKit/Palettes/MiscShell/MiscShell.subproj/RCS/MiscShell.m,v 1.2 94/08/04 17:39:39 shayman Exp Locker: shayman $
- * $Log: MiscShell.m,v $
- * Revision 1.2 94/08/04 17:39:39 shayman
- * Fiddling with string values
- *
- */
-
- /*
- * Note:
- * In several spots we do things like
- * if( [aVar isKindOfClassNamed:"DBTableView"] )
- * instead of
- * if ( [avar isKindOf:[DBTableView class]] )
- *
- * This is because you can do the first test without having to link
- * DBKit into your program. Doing the second requires linking DBKit,
- * including the DBKit header files in this program, etc etc, which
- * we'd rather avoid if we can. Same for NXTableView. Since I want this
- * object to be usable with either, this seems like the best way to
- * avoid various linking problems.
- *
- * I have done the same thing with other AppKit classes, e.g.
- * if ( [aVar isKindOfClassNamed:"Matrix"] )
- * for consistency.
- */
-
- // To avoid warnings, the following "category" defines some methods:
- // Added 1/17/95, DAY
- @interface Object(MiscShell_Warning_Suppressor)
- - setDataSource:aSource;
- - (unsigned int)columnCount;
- - columnAt:(unsigned int)aPosition;
- - identifier;
- - setIdentifier:anIdentifier;
- - reloadData:sender;
- @end
-
-
-
-
- #define MISC_SHELL_VERSION 3
-
- @interface MiscShell(PrivateMethods)
- - setScriptFromCString:(const char *)str;
- - sendText:(const char *)buffer andNewline:(BOOL)newline to:text;
- - setupEnvironment:process forSender:sender;
- - addEnvVar:(const char *)varName value:(const char *)val to:env;
- - handleCompleteLine;
- - startOutput;
- - finishOutput;
- - resort:(int)keyField;
-
- - field:(int)n ofString:s;
- - splitupCurrentLine;
- - (BOOL)useCustomDelimiters;
- - (int) fieldsInString:(MiscString *)str;
-
- @end
-
- @implementation MiscShell(PrivateMethods)
-
- - (BOOL)useCustomDelimiters
- {
- return [[self customDelimiters] length] > 0;
- }
- /*
- * Add a bunch of environment variables to a process.
- */
- - setupEnvironment:proc forSender:sender
- {
- NXBundle *mainBundle = [NXBundle mainBundle];
- id env = [proc environment]; // a MiscStringArray
-
- int i;
- char varName[3];
- id v;
-
- /*
- * Go through each of our v1...v9 instance variables in turn by
- * making use of this clever object_getInstanceVariable runtime function.
- */
- for ( i = 1; i <= 9; i++ ) {
- sprintf(varName, "v%d", i);
- // See if we have an instance variable by that name ..
- if ( object_getInstanceVariable(self, varName, (void *)&v) ) {
-
- // Now see if that obj responds to stringValue:, and if so,
- // add an environment var representing its current stringValue.
-
- // Special case: if the object is a browser, we write
- // out the value of all its selected cells, separated
- // by tabs. Rather than just the stringValue.
-
- if ( [v isKindOfClassNamed:"NXBrowser"] ) {
- List *selections = [[List alloc] init];
- MiscString *str = [[MiscString alloc] init];
- int i;
- id aCell;
-
- [v getSelectedCells:selections];
-
- i = 0;
- while ( aCell = [selections objectAt:i++] ) {
- [str catFromFormat:"%s", [aCell stringValue]];
-
- if ( i < [selections count] )
- [str catFromFormat:"\t"];
- }
-
- [self addEnvVar:varName value:[str stringValue] to:env];
- [selections free];
- [str free];
-
- } else if ( [v respondsTo:@selector(stringValue)] )
- [self addEnvVar:varName value:[v stringValue] to:env];
- }
- }
-
-
- // Add a var called "sender" containing the sender's string value
- // (if known.)
-
- // TODO - should we be using [sender selectedCell] here if it's
- // a Matrix sending us this message?
-
-
- if ( [sender respondsTo:@selector(stringValue)] )
- [self addEnvVar:"sender" value:(const char *)[sender stringValue]
- to:env];
-
- // What the hell, add a var called "senderTag" too.
- {
- char tag[10];
- if ( [sender respondsTo:@selector(tag)] ) {
- sprintf(tag, "%d", [sender tag]);
- [self addEnvVar:"senderTag" value:tag to:env];
- }
- }
-
- // TODO
- // Maybe if it's a Form sending us the message, we add a bunch
- // of variables for each entry in the Form.
-
- // Add a var called "mainBundle" giving our main application
- // directory.
-
- [self addEnvVar:"mainBundle" value:[mainBundle directory]
- to:env];
-
-
-
- return self;
- }
- // Add an environment variable to a MiscStringArray, which belongs to
- // the subprocess
-
- - addEnvVar:(const char *)varName value:(const char *)value to:env
- {
- MiscString *newStr = [[MiscString alloc] init];
- if ( ! value )
- return nil;
- [newStr catFromFormat:"%s=%s", varName, value];
- [env addString:[newStr stringValue]];
- [newStr free];
- return self;
- }
-
- // Internal method, set our script from a (char *)
-
- - setScriptFromCString:(const char *)str
- {
- if ( script )
- [script free];
- script = [MiscString newWithString:str];
- return self;
- }
- // Internal method that passes some text along to either a
- // scrolling text object (appended), or something responding to setStringValue.
- // it's appended to the end.
- // The nl variable controls whether we send a newline or not.
- // If true, and if the destination object is a Text object, we append
- // a newline.
-
- - sendText:(const char *)buffer andNewline:(BOOL) nl to:anObject
- {
- int len;
-
- if ( !anObject )
- return nil;
-
- if ( [anObject respondsTo:@selector(setStringValue:)] ) {
- return [anObject setStringValue:buffer];
- }
-
- // Otherwise it should be a text object.
- if ( ! [anObject isKindOfClassNamed:"Text"] ) {
- return nil;
- }
- len = [anObject textLength];
- [anObject setSel:len:len];
- [anObject replaceSel:buffer];
-
- if ( nl ) {
- len = [anObject textLength];
- [anObject setSel:len:len];
- [anObject replaceSel:"\n"];
- }
-
- [anObject scrollSelToVisible];
-
- return self;
- }
-
- /*
- * just read a line that looks like
- * ALERT;Are you sure?;Yes;No
- * Do an NXRunAlertPanel and write back the result as an integer.
- */
- - doAlert
- {
- MiscString *message, *b1, *b2, *b3;
- int r;
- char rbuf[10];
-
- message = [currentLine extractPart:1 useAsDelimiter: ';'];
- b1 = [currentLine extractPart:2 useAsDelimiter: ';'];
- b2 = [currentLine extractPart:3 useAsDelimiter: ';'];
- b3 = [currentLine extractPart:4 useAsDelimiter: ';'];
-
- r = NXRunAlertPanel([NXApp appName],
- [message stringValue],
- [b1 stringValue], [b2 stringValue], [b3 stringValue] );
-
- sprintf(rbuf, "%d", r);
- [process send:rbuf withNewline:YES];
-
- [message free];
- [b1 free];
- [b2 free];
- [b3 free];
-
- return self;
- }
-
- - doOpen
- {
- id op = [OpenPanel new];
- if ( [op runModalForDirectory:NULL file:NULL] ) {
- [process send: [op filename] withNewline:YES];
- } else {
- [process send:"\n" withNewline:NO];
- }
- return self;
- }
- /*
- * Do whatever is appropriate upon reading a complete line from the shell.
- */
- - handleCompleteLine
- {
-
- /*
- * Is it one of our special strings?
- */
- /*
- * Run special magic commands that write back answers when
- * we see certain strings in the output.
- */
- MiscString *var, *value;
- id varObj;
- // ALERT;Are you sure?;Yes;No
- if ( [currentLine cmp:"ALERT" n:5] == 0 ) {
- return [self doAlert];
- } else if ( [currentLine cmp:"OPEN" n:4] == 0 ) {
- return [self doOpen];
- }
-
-
- if ( [currentLine matchesRegex:"^v[0123456789]=" ] ) {
- var = [currentLine extractPart:0 useAsDelimiter:'='];
- value = [currentLine extractPart:1 useAsDelimiter:'='];
-
- // ooh tricky runtime stuff - get a pointer to the
- // instance var whose name is in "var"
- if ( object_getInstanceVariable(self, [var stringValue], (void *)&varObj) ) {
- if ( [varObj respondsTo:@selector(setStringValue:)] ) {
- [varObj setStringValue:[value stringValue]];
- }
- }
- [var free];
- [value free];
- return self;
- }
-
-
- /*
- * Add the line to our lines array.
- */
- [lines addString: [currentLine stringValue]];
-
- /*
- * Create a MiscStringArray out of it by splitting it into
- * fields, and add it to the linesBrokenIntoFields List.
- */
-
- [self splitupCurrentLine];
-
- /*
- * Send the target/action message
- */
- if ( _target && action && [_target respondsTo:action] ) {
- [_target perform:action with:self];
- }
-
- /*
- * Send the line - and a newline - to standard output.
- */
- [self sendText:[currentLine stringValue] andNewline:YES to:standardOutput];
-
- /*
- * If standard out is a browser, add a row to it.
- */
- if ( [standardOutput isKindOfClassNamed:"NXBrowser"] ) {
- id m = [standardOutput matrixInColumn:0];
- id newCell;
-
- [m renewRows: [lines count] cols:1];
- newCell = [[m cellList] lastObject];
-
-
- [newCell setStringValue:[currentLine stringValue]];
- [newCell setLeaf:YES];
- [newCell setLoaded:YES];
- [m sizeToCells];
- [standardOutput sizeToFit];
- } else if ( [standardOutput isKindOfClassNamed:"DBTableView"]
- || [standardOutput isKindOfClassNamed:"NXTableView"]) {
- ; // Don't need to do anything here, all handled in finishOutput
- // for now.
- } else if ( [standardOutput isKindOfClassNamed:"Matrix"] ) {
- [standardOutput selectCellWithTag:[self intValue]];
- } else if ( [standardOutput isKindOfClassNamed:"MiscShell"] ) {
- /*
- * Ask the other process to read our string value and
- * place it on its stdin. This is a way that you can
- * pipeline two shell objects.
- */
- [standardOutput takeStdinFrom:self];
- }
- return self;
- }
- - (int) fieldsInString:(MiscString *)str
- {
-
- if ( [self useCustomDelimiters] ) {
- // n delimiters -> n+1 fields
- return [customDelimiters numWords] + 1;
- } else if ( [self delimiter] )
- /*
- * count delimiters.
- * one delimiter = two fields.
- */
- return [str numOfChar:[self delimiter]] + 1;
- else
- /*
- * delimiter is 0 - count words delimited by whitespace
- */
- return [str numWords];
-
- }
-
-
- - splitupCurrentLine
- {
- int i;
- MiscStringArray *a = [[MiscStringArray alloc] init];
- MiscString *curLine = [[lines strings] lastObject];
-
- // Extract each field in turn, add it to the array for this line
- for ( i = 0; i < [self fieldsInString:curLine]; i++ ) {
- [a addString: [[self field:i ofString:curLine] stringValue]];
- }
-
- [linesBrokenIntoFields addObject:a];
- return self;
- }
-
- /*
- * This is here so that we can do clever things if our standardOutput
- * is a table view object. This method is automatically called when
- * the outlet is initialized.
- * We need to tell the table view that we are its data source, and
- * we need to set identifiers for each of its columns. Although those
- * identifiers are normally objects, it's apparently ok to use
- * other 32-bit quantities such as integers, so we'll
- * just set the identifier of column "i" to be "i".
- *
- * TODO - is it legal to be doing this here? is the table view set up
- * properly when setStandardOutput is called?
- */
- - setStandardOutput:newOutput
- {
- int i;
- standardOutput = newOutput;
- if ([standardOutput isKindOfClassNamed:"DBTableView"]
- || [standardOutput isKindOfClassNamed:"NXTableView"] ) {
-
- [standardOutput setDataSource:self];
- /*
- * Become its delegate so we get column moved messages
- */
- [standardOutput setDelegate:self];
- /*
- * Put numeric identifiers on each of its columns.
- */
- for ( i = 0; i < [standardOutput columnCount]; i++ )
- [[standardOutput columnAt:i] setIdentifier:(void *)i];
- }
-
- return self;
- }
-
- /*
- * startOutput does any special initialization of certain kinds of
- * output objects.
- */
-
- - startOutput
- {
- return self;
- }
-
- /*
- * finishOutput is called after the subprocess has executed, and can
- * be used to do any sort of output display cleanup.
- */
- - finishOutput
- {
- if ([standardOutput isKindOfClassNamed:"DBTableView"]
- || [standardOutput isKindOfClassNamed:"NXTableView"]) {
- [standardOutput reloadData:self];
- } else if ( [standardOutput isKindOfClassNamed:"NXBrowser"] ) {
- [standardOutput display];
- }
- return self;
- }
-
- /*
- * Here is a shellsort function, from Kernighan & Ritchie, page 116,
- * modified to pass a 3rd bonus parameter to the comparison routine.
- /*
- * Resort the output lines based on the value of column n.
- * We do this by creating a ListSortedByFields which duplicates
- * the existing string list then we tell the ListSortedByFields to
- * sort itself.
- *
- * Multiplying by sortWhenColumnsMove does the right thing for
- * ascending vs descending sorts.
- */
-
- - (int)fieldComp:(int)n forLines:(int)a :(int)b
- {
- id field1 = [self line:a field:n];
- id field2 = [self line:b field:n];
-
-
- int rval;
- // If field1 looks like a number, compare numerically.
- // TODO - do the right thing if it looks like
- // a time value (HH:MM)
-
- if ( [field1 matchesRegex:":[0-9][0-9]$" ] ) {
- // Replace the ":" with a ".", which will
- // make the comparison work just as if it was a float.
- [field1 replace: ":" with: "."];
- [field2 replace: ":" with: "."];
- }
-
- if ( [field1 matchesRegex:"^[0-9]"] ) {
- double n1, n2;
-
- n1 = atof([field1 stringValue]);
- n2 = atof( [field2 stringValue] );
-
- // Check for a K or M suffix, multiply appropriately.
- // This is so I can sort the output of "ps" nicely.
- if ( [field1 endcmp:"M"] == 0 )
- n1 *= 1024 * 1024;
- if ( [field1 endcmp:"K"] == 0 )
- n1 *= 1024;
- if ( [field2 endcmp:"M"] == 0)
- n2 *= 1024 * 1024;
- if ( [field2 endcmp:"K"] == 0)
- n2 *= 1024;
-
-
- rval = (n1 < n2) ? -1 :
- (n1 > n2) ? 1 : 0;
-
- } else {
- /*
- * Do a regular string comparison.
- */
- rval = [field1 compareTo: field2];
- }
-
- return rval * [self sortWhenColumnsMove];
- }
-
- - resort:(int)fieldNumber
- {
- List * strings = [lines strings];
- id a, b;
- int c, d, stride;
- BOOL found;
- int n = [strings count];
- /*
- * ShellSort, from the SortingInAction miniexample
- */
- #define STRIDE_FACTOR 3
- stride = 1;
- while ( stride <= n )
- stride = stride * STRIDE_FACTOR + 1;
-
- while ( stride > (STRIDE_FACTOR - 1)) {
- stride = stride / STRIDE_FACTOR;
- for ( c = stride; c < n; c++ ) {
- found = NO;
- d = c - stride;
- while ( (d >= 0) && !found ) {
-
- if ( [self fieldComp:fieldNumber forLines:d:d+stride] > 0 ) {
- // Swap the "lines" array ...
- a = [strings objectAt:d];
- b = [strings objectAt:d+stride];
- [strings replaceObjectAt:d with:b];
- [strings replaceObjectAt:d+stride with:a];
-
-
- //and the linesBrokenIntoFields list
- a = [linesBrokenIntoFields objectAt:d];
- b = [linesBrokenIntoFields objectAt:d+stride];
- [linesBrokenIntoFields replaceObjectAt:d with:b];
- [linesBrokenIntoFields replaceObjectAt:d+stride with:a];
-
- d -= stride;
- } else
- found = YES;
- }
- }
- }
-
- return self;
- }
-
- /*
- * Utility routine to return a particular field of a particular string,
- * using our delimiter. If delimiter is 0, we look for blank-separated words;
- * otherwise we look for the particular delimited field.
- */
- - field:(int)f ofString:s
- {
- id thisField = nil;
- int startPos, endPos;
- MiscString *delim;
-
- if ( [self useCustomDelimiters] ) {
- // customDelimiters is a string like this
- // 0-4 7-9 13-22
- // that defines the boundaries of each field. So, in this
- // case, if we want field 2, we extract chars 13-22.
-
- delim = [[self customDelimiters] wordNum:f];
-
- // delim is now something like "5-9", extract the
- // starting and ending positions. If it's "72-", that
- // means "72 to end of line"
-
- if ( [delim stringValue] && (sscanf( [delim stringValue], "%d-%d", &startPos, &endPos) == 2) ) {
- thisField = [s midFrom:startPos to:endPos];
- } else if ( [delim stringValue ] && (sscanf( [delim stringValue], "%d-", &startPos) == 1) ) {
- thisField = [s midFrom:startPos to: [s length]];
- }
- [delim free];
- } else if ( [self delimiter] )
- thisField = [s extractPart:f
- useAsDelimiter:[self delimiter]];
- else {
- /*
- * take the column'th word.
- * Current bug in MiscString: wordNum:n for n > numWords returns
- * the last word rather than nil. So we check that.
- */
- /* if ( f < [s numWords] )
- thisField = [s wordNum:f];
- else
- thisField = nil;
- */ /* I fixed the MiscString bug, so I'm taking this out. -don */
- thisField = [s wordNum:f];
- }
-
- return thisField;
- }
-
- @end
-
-
- @implementation MiscShell(TableViewDelegate)
- - (unsigned int) rowCount
- {
- return [self lineCount];
- }
-
- - (unsigned int) columnCount
- {
- return 1; // ??? todo - what do I put here? does it matter?
- }
-
- /*
- * This is a table view asking for the value at row aPosition,
- * column identifier.
- */
- - getValueFor:identifier at:(unsigned int)aPosition into:aValue
- {
- [aValue setStringValue:
- [[self line:aPosition field:(int)identifier] stringValue] ];
- return self;
- }
-
- /*
- * If you're the delegate of a DBTableView, you get these messages
- * when the columns are resized. Not sure just how I want to deal
- * with this yet.
- *
- * Current Plan - check the sortWhenColumnsMove variable.
- * If 0, do nothing.
- * If 1, resort ascending
- * If -1, resort descending
- */
- - tableView:sender movedColumnFrom:(unsigned int) old to:(unsigned int) new
- {
-
- /*
- * Resort based on the identifier of the new first column.
- */
- if ( [self sortWhenColumnsMove] ) {
- [self resort: (int)[[sender columnAt:0] identifier]];
- [sender reloadData:self];
- }
- return self;
- }
-
- @end
-
- @implementation MiscShell
-
- + initialize
- {
- if (self == [MiscShell class]) {
- /*
- * **** Archiving: READ ME **** After bumping the _VERSION, it is
- * considered common practice to add a comment line indicating the new
- * version number, date, and modifier. Optionally, the reason for the
- * change. There is no need to modify the setVersion message. BJM
- * 5/24/94
- */
- // version 0: initial. (sah)
- // version 1: adds customDelimiters var. (sah, sep 13 1994)
- // version 2: adds linesBrokenIntoFields array (sah, sep 14 1994)
- // version 3: fixes a bug with archiving BOOL vars (sah, jan 4 1995)
- [[MiscShell class] setVersion:MISC_SHELL_VERSION];
- }
-
- return self;
- }
-
- - init
- {
- self = [super init];
- script = [[MiscString alloc] init];
- fullOutput = [[MiscString alloc] init];
- currentLine = [[MiscString alloc] init];
- lines = [[MiscStringArray alloc] init];
-
- [self setDelimiter:0]; // means "parse words"
- [self setRunToCompletion:NO]; // run asynchronously
- [self setSortWhenColumnsMove:NO];
-
- linesBrokenIntoFields = [[List alloc] init];
- return self;
- }
-
- /*
- * Initialize, and run a non-interactive command and wait for it to terminate.
- */
- - initWithCommand:(const char *)cmd
- {
- [self init];
- [self setRunToCompletion:YES];
- [self setScriptFromCString:cmd];
- [self executeScript:self];
-
- return self;
- }
-
-
- - free
- {
- if ( script )
- script = [script free];
- if ( process ) {
- [process terminate:self];
- process = [process free];
- }
- if ( fullOutput )
- fullOutput = [fullOutput free];
- [currentLine free];
- return [super free];
- }
-
- // Archiving methods
-
- - read:(NXTypedStream *)stream
- {
- int version;
- Class myClass = [self class];
- int int1, int2; // compensate for old read/write bugs
-
- [super read:stream];
- version = NXTypedStreamClassVersion(stream, "MiscShell");
-
- switch (version) {
- case MISC_SHELL_VERSION: {
- /*
- * Version 3 correctly reads/writes BOOL vars as "c", not "i"
- */
- standardOutput = NXReadObject(stream);
- standardInput = NXReadObject(stream);
- standardError = NXReadObject(stream);
- v1 = NXReadObject(stream);
- v2 = NXReadObject(stream);
- v3 = NXReadObject(stream);
- v4 = NXReadObject(stream);
- script = NXReadObject( stream );
- _target = NXReadObject( stream );
- NXReadTypes(stream, ":", &action);
- NXReadTypes(stream, "@", &process);
- NXReadTypes(stream, "@", &fullOutput);
- NXReadTypes(stream, "c", &executionInProgress);
- NXReadTypes(stream, "@", ¤tLine);
- NXReadTypes(stream, "cc", ¤tLineIsComplete, &runToCompletion);
- NXReadTypes(stream, "@", &lines);
- NXReadTypes(stream, "c", &delimiter);
- NXReadTypes(stream, "c", &sortWhenColumnsMove);
- NXReadTypes(stream, "@", &customDelimiters);
- NXReadTypes(stream, "@", &linesBrokenIntoFields); // new for v2
- break;
- }
- case 2: {
- /*
- * Version 2 adds the linesBrokenIntoFields var
- */
- standardOutput = NXReadObject(stream);
- standardInput = NXReadObject(stream);
- standardError = NXReadObject(stream);
- v1 = NXReadObject(stream);
- v2 = NXReadObject(stream);
- v3 = NXReadObject(stream);
- v4 = NXReadObject(stream);
- script = NXReadObject( stream );
- _target = NXReadObject( stream );
- NXReadTypes(stream, ":", &action);
- NXReadTypes(stream, "@", &process);
- NXReadTypes(stream, "@", &fullOutput);
- // Written as int in versions <= 2, but really a char
- NXReadTypes(stream, "i", &int1); executionInProgress = int1;
- NXReadTypes(stream, "@", ¤tLine);
- // Written as int in versions <= 2, but really a char
- NXReadTypes(stream, "ii", &int1, &int2);
- currentLineIsComplete = int1;
- runToCompletion = int2;
- NXReadTypes(stream, "@", &lines);
- NXReadTypes(stream, "c", &delimiter);
- // Written as int in versions <= 2, but really a char
- NXReadTypes(stream, "i", &int1); sortWhenColumnsMove = int1;
- NXReadTypes(stream, "@", &customDelimiters);
- NXReadTypes(stream, "@", &linesBrokenIntoFields); // new for v2
- break;
- }
- case 1: {
- /*
- * Version 1 adds a customDelimiters instance var
- */
- standardOutput = NXReadObject(stream);
- standardInput = NXReadObject(stream);
- standardError = NXReadObject(stream);
- v1 = NXReadObject(stream);
- v2 = NXReadObject(stream);
- v3 = NXReadObject(stream);
- v4 = NXReadObject(stream);
- script = NXReadObject( stream );
- _target = NXReadObject( stream );
- NXReadTypes(stream, ":", &action);
- NXReadTypes(stream, "@", &process);
- NXReadTypes(stream, "@", &fullOutput);
- // Written as int in versions <= 2, but really a char
- NXReadTypes(stream, "i", &int1); executionInProgress = int1;
- NXReadTypes(stream, "@", ¤tLine);
- // Written as int in versions <= 2, but really a char
- NXReadTypes(stream, "ii", &int1, &int2);
- currentLineIsComplete = int1;
- runToCompletion = int2;
- NXReadTypes(stream, "@", &lines);
- NXReadTypes(stream, "c", &delimiter);
- // Written as int in versions <= 2, but really a char
- NXReadTypes(stream, "i", &int1); sortWhenColumnsMove = int1;
- NXReadTypes(stream, "@", &customDelimiters);
- linesBrokenIntoFields = [[List alloc] init]; // new - need one
- break;
- }
- case 0: {
- standardOutput = NXReadObject(stream);
- standardInput = NXReadObject(stream);
- standardError = NXReadObject(stream);
- v1 = NXReadObject(stream);
- v2 = NXReadObject(stream);
- v3 = NXReadObject(stream);
- v4 = NXReadObject(stream);
- script = NXReadObject( stream );
- _target = NXReadObject( stream );
- NXReadTypes(stream, ":", &action);
- NXReadTypes(stream, "@", &process);
- NXReadTypes(stream, "@", &fullOutput);
- // Written as int in versions <= 2, but really a char
- NXReadTypes(stream, "i", &int1); executionInProgress = int1;
- NXReadTypes(stream, "@", ¤tLine);
- // Written as int in versions <= 2, but really a char
- NXReadTypes(stream, "ii", &int1, &int2);
- currentLineIsComplete = int1;
- runToCompletion = int2;
- NXReadTypes(stream, "@", &lines);
- NXReadTypes(stream, "c", &delimiter);
- // Written as int in versions <= 2, but really a char
- NXReadTypes(stream, "i", &int1); sortWhenColumnsMove = int1;
- linesBrokenIntoFields = [[List alloc] init]; // new - need one
- break;
- }
- default: {
- NXLogError("[%s %s] - unknown version of %s in typed stream",
- [myClass name], sel_getName(_cmd),
- [myClass name]);
- break;
- }
- }
- return self;
- }
-
- - write:(NXTypedStream *)stream
- {
- [super write:stream];
-
- NXWriteObjectReference( stream, standardOutput );
- NXWriteObjectReference( stream, standardInput );
- NXWriteObjectReference( stream, standardError );
- NXWriteObjectReference( stream, v1 );
- NXWriteObjectReference( stream, v2 );
- NXWriteObjectReference( stream, v3 );
- NXWriteObjectReference( stream, v4 );
- NXWriteObject( stream, script );
- NXWriteObjectReference( stream, _target );
- NXWriteTypes(stream, ":", &action);
-
- NXWriteTypes(stream, "@", &process);
- NXWriteTypes(stream, "@", &fullOutput);
- NXWriteTypes(stream, "c", &executionInProgress); // BOOL
- NXWriteTypes(stream, "@", ¤tLine);
- NXWriteTypes(stream, "cc", ¤tLineIsComplete, &runToCompletion); // BOOL
- NXWriteTypes(stream, "@", &lines);
- NXWriteTypes(stream, "c", &delimiter);
- NXWriteTypes(stream, "c", &sortWhenColumnsMove);
- NXWriteTypes(stream, "@", &customDelimiters); // added in v1
- NXWriteTypes(stream, "@", &linesBrokenIntoFields); // added in v2
-
- return self;
- }
-
- - target { return _target; }
- - setTarget:aTarget
- {
- _target = aTarget;
- return self;
- }
- - (SEL)action { return action; }
- - setAction:(SEL)anAction
- {
- action = anAction;
- return self;
- }
-
- /*
- * Our "string value" is the "current" line we've received
- * (which doesn't contain a newline.)
- */
-
- - (const char *)stringValue
- {
- return [currentLine stringValue];
- }
-
- /*
- * Int, double, float values are just derived from our string value.
- * note: no error checking as to whether the string value really is
- * a number.
- */
-
- - (double) doubleValue
- {
- return ( atof([self stringValue]) );
- }
-
- - (float) floatValue
- {
- return ( (float) atof([self stringValue]) );
- }
- - (int) intValue
- {
- return ( atoi([self stringValue]) );
- }
-
-
- /*
- * Set and retrieve the actual script.
- * We store it internally as a MiscString
- */
-
- - (MiscString *)script
- {
- return script;
- }
-
- // Inspector sends this when the script changes
- - setScript:(MiscString *)newScript
- {
- return [self setScriptFromCString: [newScript stringValue]];
- }
- - (MiscString *)customDelimiters
- {
- return customDelimiters;
- }
-
- // Inspector sends this when the script changes
- - setCustomDelimiters:(MiscString *)d
- {
- [customDelimiters free];
- customDelimiters = [d copy];
- return self;
- }
-
- // Messages from Controls
-
- - executeScript:sender
- {
-
- if ( executionInProgress ) {
- NXBeep();
- return nil;
- }
-
- if ( process ) {
- [process terminate:self];
- [process free];
- }
-
- /*
- * Get rid of old accumulated output.
- */
-
- [fullOutput setStringValue:""];
- [currentLine setStringValue:""];
- currentLineIsComplete = NO;
- [[lines strings] freeObjects];
-
- [linesBrokenIntoFields freeObjects];
-
- /*
- * Here we have special pre-output checks for certain kinds of
- * output objects.
- */
- [self startOutput];
-
- // Create a subprocess to execute the script. Don't run it just yet.
-
- process = [[MiscSubprocess alloc] init:NULL withDelegate:self];
-
-
- // Set up subprocess environment here - a bunch of
- // environment variables that tell the process about
- // the values of v1, v2, etc.
-
- [self setupEnvironment:process forSender:sender];
-
- // And finally start the process going.
-
- [process execute:[script stringValue]
- withPtys:NO
- asynchronously: ![self runToCompletion]];
-
- return self;
- }
-
- /*
- * These messages might arrive from either a control or a matrix of
- * controls.
- */
-
- - executeFromStringValue:sender
- {
- if ( [sender isKindOfClassNamed:"Matrix"] )
- sender = [sender selectedCell];
-
- [self setScriptFromCString:[sender stringValue]];
- return [self executeScript:sender];
- }
- - executeFromTitle:sender
- {
- if ( [sender isKindOfClassNamed:"Matrix"] )
- sender = [sender selectedCell];
-
- [self setScriptFromCString:[sender title]];
- return [self executeScript:sender];
- }
-
- - executeFromAltTitle:sender
- {
- if ( [sender isKindOfClassNamed:"Matrix"] )
- sender = [sender selectedCell];
-
- [self setScriptFromCString:[sender altTitle]];
- return [self executeScript:sender];
- }
-
- - pause:sender
- {
- return [process pause:sender];
- }
- - resume:sender
- {
- return [process resume:sender];
- }
-
- - terminate:sender
- {
- return [process terminate:sender];
- }
-
- /*
- * Methods that return particular lines, or fields within lines.
- */
-
- - (int)lineCount {
- return [lines count];
- }
-
- - (int) fieldsInLine:(int)n
- {
- return [[linesBrokenIntoFields objectAt:n] count];
- }
-
- - (MiscStringArray *)lines { return lines; }
-
- /*
- * Return a copy of the MiscString that holds line number n.
- * We return a copy for consistency with the line:field: method, so that*
- * the caller is responsible for freeing both.
- * Just returning [[lines strings] objectAt:n] would hand back a string
- * owned by the MiscStringArray object, which the caller shouldn't free.
- *
- * Boy it will be nice when libFoundation_s.a is available everywhere
- * and we can do this more rationally.
- */
- - (MiscString *)line:(int)n
- {
- id str = [[lines strings] objectAt:n];
- if ( str )
- return [str copy];
- else
- return nil;
- }
-
- /*
- * Return the MiscString representing field f of line n;
- * the caller should NOT free it.
- */
- - (MiscString *)line:(int)n field:(int)f
- {
- return [[[linesBrokenIntoFields objectAt:n] strings] objectAt:f];
- }
-
-
-
- // MiscSubprocess delegate methods
- - subprocess:sender output:(const char *)buffer
- {
-
- /*
- * fullOutput records the entire output of the script, so add
- * the buffer to the end.
- */
- [fullOutput cat:buffer];
-
- /*
- * Now. We have to decide how this new chunk of data, with
- * possibly embedded newlines, affects the current line. We want
- * to fire off a target/action message every time we receive a newline.
- */
-
-
- while ( *buffer ) {
-
- /*
- * If we have previously accumulated a complete newline, it's
- * no longer complete.
- */
- if( currentLineIsComplete ) {
- [currentLine setStringValue:""];
- currentLineIsComplete = NO;
- }
-
- /*
- * If we are looking at a newline, then the current output
- * line is complete, so fire the target/action message.
- */
- if ( *buffer == '\n' ) {
-
- [self handleCompleteLine];
- currentLineIsComplete = YES;
-
-
- } else {
- /*
- * Add this non-newline character
- */
- [currentLine addChar:*buffer];
- }
- buffer++;
-
- }
- return self;
- }
-
- // todo - if standardError and standardOutput are the same, why not
- // just forward this message to subprocess:stdoutOutput, which would merge
- // stderr and stdout handling (and allow target/action for stderr messages)
- - subprocess:sender stderrOutput:(const char *)buffer
- {
- if ( standardError )
- [self sendText:buffer andNewline:NO to:standardError];
- else
- fputs( buffer, stderr ); // To the console.
- return self; // added to remove warning... -- DAY
- }
-
- - subprocess:sender done:(int)status :(MiscSubprocessEndCode)code
- {
- executionInProgress = NO;
-
- [self finishOutput];
- return self;
- }
-
-
- - (BOOL) runToCompletion { return runToCompletion; }
- - setRunToCompletion:(BOOL)c;
- {
- runToCompletion = c;
- return self;
- }
- - (int) sortWhenColumnsMove { return sortWhenColumnsMove; }
- - setSortWhenColumnsMove:(int)i
- {
- sortWhenColumnsMove = i;
- return self;
- }
- - (char)delimiter { return delimiter; }
- - setDelimiter:(char)c
- {
- delimiter = c;
- return self;
- }
-
-
- - setExecArgs:(const char *)a1:(const char *)a2:(const char *)a3
- {
- ; // TODO - finish me
- return self;
- }
-
- /*
- * Methods for sending data to the standard input of a process.
- */
-
- /*
- * takeStdinFrom:sender writes [sender stringValue] followed by a newline
- * to the process.
- */
- - takeStdinFrom:sender
- {
- [self writeToStdin:[sender stringValue]];
- [self writeToStdin:"\n"];
- return self;
- }
-
- /*
- * writeToStdin writes a string verbatim to the standard input of the
- * process.
- */
-
- - writeToStdin:(const char *)str
- {
- [process send:str withNewline:NO];
- return self;
- }
-
-
- @end
-